import { POP, PUSH, REPLACE, PopStateEvent, stripBasename, supportsHistory, createKey } from "./utils/index";
import { Manager } from "./TransitionManager";
import { createPath } from "./createPath";
import { createLocation } from "./createLocation";

/**
 * ! 创建一个 BrowserHistory API 对象
 * ! 核心逻辑是根据浏览器中 history 对象来维护地址栈(注意ie浏览器老版本不支持 history 模式)
 * @param {*} options 
 */
export function createBrowserHistory(options = {}) {
    if (!!!(typeof window !== 'undefined' && window.document && window.document.createElement)) {
        console.error("Browser history needs a DOM")
    }
    // 解构参数配置项并且赋默认值
    const {
        basename = "",
        forceRefresh = false,
        keyLength = 6,
        getUserConfirmation = (message, callback) => callback(window.confirm(message))
    } = options;
    // 监听器以及阻塞模块
    const transitionManager = new Manager(getUserConfirmation);
    // 初始化 location
    const location = getDOMLocation(window.history.state || {});
    // 用于记录 location 栈中对应数据(后期可以通过计算回退地址)
    let allKeys = [location.key];
    // 设置开关阻塞只能添加一次
    let isBlocked = false;
    /**
     * 添加阻塞
     * @param {*} prompt 可以字符串也可以是函数
     * @returns 
     */
    function block(prompt) {
        prompt = prompt || false;
        const unblock = transitionManager.setPrompt(prompt);
        if (!isBlocked) {
            checkDOMListeners(1); // 设置阻塞就要开启监听状态变化
            isBlocked = true;
        }
        return () => {
            if (isBlocked) {
                isBlocked = false;
                checkDOMListeners(-1);
            }
            return unblock();
        };
    }
    /**
     * 产生带基础路径的完整路径
     * @param {*} location 
     * @returns 
     */
    function createHref(location) {
        return basename + createPath(location);
    }
    /**
     * 前进后退
     * @param {*} n 步数
     */
    function go(n) {
        window.history.go(n);
    }
    /**
     * 后退方法
     */
    function goBack() {
        window.history.back();
    }
    /**
     * 前进方法
     */
    function goForward() {
        window.history.forward();
    }
    // 用于阻塞点击浏览器回退前进时回退时不进行产生阻塞
    let forceNextPop = false;
    /**
     * 处理状态改变事件
     * @param {*} event 
     */
    function handlePopState(event) {
        // 判断是否有状态
        if (event.state === undefined && navigator.userAgent.indexOf('CriOS') === -1) return;
        const location = getDOMLocation(event.state); // 回退地址的 location 对象
        // 回退时不需要进行触发阻塞但是监听器需要
        if (forceNextPop) {
            forceNextPop = false;
            // 浏览器点击回退(不能阻止浏览器默认行为)地址会先改变，但是阻塞给定假可以看到回退地址(实际上进行了地址回撤)回撤过程还是会触发监听事件
            transitionManager.tiggerListener(location, history.action);
        } else {
            const action = POP; // 浏览器前进后退 action 固定为 POP
            // 触发阻塞器
            transitionManager.triggerPrompt(location, action, function (ok) {
                // 只有回调返回为真返回之前地址
                if (ok) {
                    Object.assign(history, { action, location }); // 合并 history 对象
                    transitionManager.tiggerListener(location, action);
                } else {
                    revertPop(location); // 还原之前地址数据
                }
            });
        }
    }
    /**
     * 回调之前地址(可能前进也可能后退) 通过记录状态 key 位置辨识
     * @param {*} fromLocation 
     */
    function revertPop(fromLocation) {
        const toLocation = history.location;// 需要回退地址 location
        //! 如何果知道回退是前进还是后退，核心还是通过维护一个 key 状态数组索引位置知道前进后退
        let toIndex = allKeys.indexOf(toLocation.key);
        if (toIndex === -1) toIndex = 0;
        let fromIndex = allKeys.indexOf(fromLocation.key);
        if (fromIndex === -1) fromIndex = 0;
        const delta = toIndex - fromIndex; // -1 或者 1 
        if (delta) {
            forceNextPop = true;
            go(delta); // 进行撤回上步操作
        }
    }
    //? 触发取消监听器地址状态变化函数(很巧妙)
    let listenerCount = 0;
    function checkDOMListeners(delta) {
        listenerCount += delta;
        if (listenerCount === 1 && delta === 1) {
            window.addEventListener(PopStateEvent, handlePopState);  // 监听状态改变
        } else if (listenerCount === 0) {
            window.removeEventListener(PopStateEvent, handlePopState); // 取消状态监听
        }
    }
    /**
     * 监听地址栏变化
     * @param {*} listener 
     * @returns 取消监听函数
     */
    function listen(listener) {
        const unlisten = transitionManager.appendListener(listener);
        checkDOMListeners(1); // 设置监听器开始监听地址栏变化
        return () => {
            checkDOMListeners(-1); // 取消监听
            unlisten();
        };
    }
    /**
     * 获取地址栏 history 状态
     * @param {*} historyState 
     * @returns 
     */
    function getDOMLocation(historyState) {
        const { key, state } = historyState || {};
        let { pathname, search, hash } = window.location;
        let path = pathname + search + hash;
        basename && (path = stripBasename(path, basename));  // 删除基础路径
        return createLocation(path, state, key);
    }
    /**
     * 追加地址入栈
     * @param {*} path  可以是字符串也可以是对象
     * @param {*} state 可选状态参数
     */
    function push(path, state) {
        changePagePath(path, state, true);
    }
    /**
     * 替换当前地址
     * @param {*} path 
     * @param {*} state 
     */
    function replace(path, state) {
        changePagePath(path, state, false);
    }
    /**
     * 改变页面地址
     * @param {*} path 路径字符串或对象
     * @param {*} state 状态数据
     * @param {*} isPush  是否 push 方法
     */
    function changePagePath(path, state, isPush) {
        if (typeof path === 'object' && path.state !== undefined && state !== undefined) {
            console.warn("You should avoid providing a 2nd state argument to push when the 1st")
        }
        const action = isPush ? PUSH : REPLACE;
        // 创建一个需要跳转新 location 对象
        const location = createLocation(path, state, createKey(keyLength), history.location);
        //? 阻塞通过后执行跳转页面操作
        transitionManager.triggerPrompt(location, action, (ok) => {
            if (!ok) return; // 回调参数只有真才进行地址跳转
            const path = createHref(location); // 完整地址栏路径
            const key = location.key;
            const state = location.state;
            // 不支持 history 模式
            if (!supportsHistory()) {
                if (state !== undefined) {
                    console.warn(`Browser history cannot ${isPush ? "push" : "repalce"} state in browsers that do not support HTML5 history`)
                }
                isPush ? (window.location.href = path) : window.location.replace(path);
                return;
            }
            if (isPush) {
                window.history.pushState({ key, state }, null, path);  // 设置状态数据
            } else {
                window.history.replaceState({ key, state }, null, path);
            }
            // 是否强制刷新
            if (forceRefresh) {
                window.location.href = path; // 强制刷新一定要在以及更改后完成
            } else {
                if (isPush) {
                    let prevIndex = allKeys.indexOf(history.location.key);
                    let nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
                    nextKeys.push(location.key);
                    allKeys = nextKeys;
                } else {
                    let prevIndex = allKeys.indexOf(history.location.key);
                    if (prevIndex !== -1) allKeys[prevIndex] = location.key; // 替换
                }
                Object.assign(history, { action, location });
                // 发布地址栏改变时事件
                transitionManager.tiggerListener(location, action);
            }
        });
        //! 注意阻塞优先于监听地址变化(监听事件在阻塞发布后执行)
    }
    // 返回出去 history 对象包含路由中 history 核心信息
    const history = {
        action: POP,
        block,
        createHref,
        go,
        goBack,
        goForward,
        length: window.history.length,
        listen,
        location,
        push,
        replace,
    };
    return history;
}

window.createBrowserHistory2 = createBrowserHistory();